Skip to main content
On this page

Testing

Deno provides a built-in test runner for writing and running tests in both JavaScript and TypeScript. This makes it easy to ensure your code is reliable and functions as expected without needing to install any additional dependencies or tools. The deno test runner allows you fine-grained control over permissions for each test, ensuring that code does not do anything unexpected.

In addition to the built-in test runner, you can also use other test runners from the JS ecosystem, such as Jest, Mocha, or AVA, with Deno. We will not cover these in this document however.

Writing Tests Jump to heading

To define a test in Deno, you use the Deno.test() function. Here are some examples:

my_test.ts
import { assertEquals } from "jsr:@std/assert";

Deno.test("simple test", () => {
  const x = 1 + 2;
  assertEquals(x, 3);
});

import { delay } from "jsr:@std/async";

Deno.test("async test", async () => {
  const x = 1 + 2;
  await delay(100);
  assertEquals(x, 3);
});

Deno.test({
  name: "read file test",
  permissions: { read: true },
  fn: () => {
    const data = Deno.readTextFileSync("./somefile.txt");
    assertEquals(data, "expected content");
  },
});

If you prefer a "jest-like" expect style of assertions, the Deno standard library provides an expect function that can be used in place of assertEquals:

my_test.ts
import { expect } from "jsr:@std/expect";
import { add } from "./add.js";

Deno.test("add function adds two numbers correctly", () => {
  const result = add(2, 3);
  expect(result).toBe(5);
});

Running Tests Jump to heading

To run your tests, use the deno test subcommand.

If run without a file name or directory name, this subcommand will automatically find and execute all tests in the current directory (recursively) that match the glob {*_,*.,}test.{ts, tsx, mts, js, mjs, jsx}.

# Run all tests in the current directory and all sub-directories
deno test

# Run all tests in the util directory
deno test util/

# Run just my_test.ts
deno test my_test.ts

# Run test modules in parallel
deno test --parallel

# Pass additional arguments to the test file that are visible in `Deno.args`
deno test my_test.ts -- -e --foo --bar

# Provide permission for deno to read from the filesystem, which is necessary
# for the final test above to pass
deno test --allow-read my_test.ts

Test Steps Jump to heading

Deno also supports test steps, which allow you to break down tests into smaller, manageable parts. This is useful for setup and teardown operations within a test:

Deno.test("database operations", async (t) => {
  using db = await openDatabase();
  await t.step("insert user", async () => {
    // Insert user logic
  });
  await t.step("insert book", async () => {
    // Insert book logic
  });
});

Command line filtering Jump to heading

Deno allows you to run specific tests or groups of tests using the --filter option on the command line. This option accepts either a string or a pattern to match test names. Filtering does not affect steps; if a test name matches the filter, all of its steps are executed.

Consider the following tests:

Deno.test("my-test", () => {});
Deno.test("test-1", () => {});
Deno.test("test-2", () => {});

Filtering by string Jump to heading

To run all tests that contain the word "my" in their names, use:

deno test --filter "my" tests/

This command will execute my-test because it contains the word "my".

Filtering by Pattern Jump to heading

To run tests that match a specific pattern, use:

deno test --filter "/test-*\d/" tests/

This command will run test-1 and test-2 because they match the pattern test-* followed by a digit.

To indicate that you are using a pattern (regular expression), wrap your filter value with forward slashes /, much like JavaScript’s syntax for regular expressions.

Including and excluding test files in the configuration file Jump to heading

You can also filter tests by specifying paths to include or exclude in the Deno configuration file.

For example, if you want to only test src/fetch_test.ts and src/signal_test.ts and exclude everything in out/:

{
  "test": {
    "include": [
      "src/fetch_test.ts",
      "src/signal_test.ts"
    ]
  }
}

Or more likely:

{
  "test": {
    "exclude": ["out/"]
  }
}

Test definition selection Jump to heading

Deno provides two options for selecting tests within the test definitions themselves: ignoring tests and focusing on specific tests.

Ignoring/Skipping Tests Jump to heading

You can ignore certain tests based on specific conditions using the ignore boolean in the test definition. If ignore is set to true, the test will be skipped. This is useful, for example, if you only want a test to run on a specific operating system.

Deno.test({
  name: "do macOS feature",
  ignore: Deno.build.os !== "darwin", // This test will be ignored if not running on macOS
  fn() {
    // do MacOS feature here
  },
});

If you want to ignore a test without passing any conditions, you can use the ignore() function from the Deno.test object:

Deno.test.ignore("my test", () => {
  // your test code
});

Only Run Specific Tests Jump to heading

If you want to focus on a particular test and ignore the rest, you can use the only option. This tells the test runner to run only the tests with only set to true. Multiple tests can have this option set. However, if any test is flagged with only, the overall test run will always fail, as this is intended to be a temporary measure for debugging.

Deno.test.only("my test", () => {
  // some test code
});

or

Deno.test({
  name: "Focus on this test only",
  only: true, // Only this test will run
  fn() {
    // test complicated stuff here
  },
});

Failing fast Jump to heading

If you have a long-running test suite and wish for it to stop on the first failure, you can specify the --fail-fast flag when running the suite.

deno test --fail-fast

This will cause the test runner to stop execution after the first test failure.

Reporters Jump to heading

Deno includes three built-in reporters to format test output:

  • pretty (default): Provides a detailed and readable output.
  • dot: Offers a concise output, useful for quickly seeing test results.
  • junit: Produces output in JUnit XML format, which is useful for integrating with CI/CD tools.

You can specify which reporter to use with the --reporter flag:

# Use the default pretty reporter
deno test

# Use the dot reporter for concise output
deno test --reporter=dot

# Use the JUnit reporter
deno test --reporter=junit

Additionally, you can write the JUnit report to a file while still getting human-readable output in the terminal by using the --junit-path flag:

deno test --junit-path=./report.xml

Spying, mocking (test doubles), stubbing and faking time Jump to heading

The Deno Standard Library provides a set of functions to help you write tests that involve spying, mocking, and stubbing. Check out the @std/testing documentation on JSR for more information on each of these utilities.

Coverage Jump to heading

Deno will collect test coverage into a directory for your code if you specify the --coverage flag when starting deno test. This coverage information is acquired directly from the V8 JavaScript engine, ensuring high accuracy.

This can then be further processed from the internal format into well known formats like lcov with the deno coverage tool.

Behavior-Driven Development Jump to heading

With the @std/testing/bdd module you can write your tests in a familiar format for grouping tests and adding setup/teardown hooks used by other JavaScript testing frameworks like Jasmine, Jest, and Mocha.

The describe function creates a block that groups together several related tests. The it function registers an individual test case. For example:

import { describe, it } from "jsr:@std/testing/bdd";
import { expect } from "jsr:@std/expect";
import { add } from "./add.js";

describe("add function", () => {
  it("adds two numbers correctly", () => {
    const result = add(2, 3);
    expect(result).toBe(5);
  });

  it("handles negative numbers", () => {
    const result = add(-2, -3);
    expect(result).toBe(-5);
  });
});

Check out the documentation on JSR for more information on these functions and hooks.

Documentation Tests Jump to heading

Deno allows you to evaluate code snippets written in JSDoc or markdown files. This ensures the examples in your documentation are up-to-date and functional.

Example code blocks Jump to heading

example.ts
/**
 * # Examples
 *
 * ```ts
 * import { assertEquals } from "jsr:@std/assert/equals";
 *
 * const sum = add(1, 2);
 * assertEquals(sum, 3);
 * ```
 */
export function add(a: number, b: number): number {
  return a + b;
}

The triple backticks mark the start and end of code blocks, the language is determined by the language identifier attribute which may be one of the following:

  • js
  • javascript
  • mjs
  • cjs
  • jsx
  • ts
  • typescript
  • mts
  • cts
  • tsx

If no language identifier is specified then the language is inferred from media type of the source document that the code block is extracted from.

deno test --doc example.ts

The above command will extract this example, turn it into a pseudo test case that looks like below:

example.ts$4-10.ts
import { assertEquals } from "jsr:@std/assert/equals";
import { add } from "file:///path/to/example.ts";

Deno.test("example.ts$4-10.ts", async () => {
  const sum = add(1, 2);
  assertEquals(sum, 3);
});

and then run it as a standalone module living in the same directory as the module being documented.

Want to type-check only?

If you want to type-check your code snippets in JSDoc and markdown files without actually running them, you can use deno check command with --doc option (for JSDoc) or with --doc-only option (for markdown) instead.

Exported items are automatically imported Jump to heading

Looking at the generated test code above, you will notice that it includes the import statement to import the add function even though the original code block does not have it. When documenting a module, any items exported from the module are automatically included in the generated test code using the same name.

Let's say we have the following module:

example.ts
/**
 * # Examples
 *
 * ```ts
 * import { assertEquals } from "jsr:@std/assert/equals";
 *
 * const sum = add(ONE, getTwo());
 * assertEquals(sum, 3);
 * ```
 */
export function add(a: number, b: number): number {
  return a + b;
}

export const ONE = 1;
export default function getTwo() {
  return 2;
}

This will get converted to the following test case:

example.ts$4-10.ts
import { assertEquals } from "jsr:@std/assert/equals";
import { add, ONE }, getTwo from "file:///path/to/example.ts";

Deno.test("example.ts$4-10.ts", async () => {
  const sum = add(ONE, getTwo());
  assertEquals(sum, 3);
});

Skipping code blocks Jump to heading

You can skip the evaluation of code blocks by adding the ignore attribute.

/**
 * This code block will not be run.
 *
 * ```ts ignore
 * await sendEmail("deno@example.com");
 * ```
 */
export async function sendEmail(to: string) {
  // send an email to the given address...
}

Sanitizers Jump to heading

The test runner offers several sanitizers to ensure that the test behaves in a reasonable and expected way.

Resource sanitizer Jump to heading

The resource sanitizer ensures that all I/O resources created during a test are closed, to prevent leaks.

I/O resources are things like Deno.FsFile handles, network connections, fetch bodies, timers, and other resources that are not automatically garbage collected.

You should always close resources when you are done with them. For example, to close a file:

const file = await Deno.open("hello.txt");
// Do something with the file
file.close(); // <- Always close the file when you are done with it

To close a network connection:

const conn = await Deno.connect({ hostname: "example.com", port: 80 });
// Do something with the connection
conn.close(); // <- Always close the connection when you are done with it

To close a fetch body:

const response = await fetch("https://example.com");
// Do something with the response
await response.body?.cancel(); // <- Always cancel the body when you are done with it, if you didn't consume it otherwise

This sanitizer is enabled by default, but can be disabled in this test with sanitizeResources: false:

Deno.test({
  name: "leaky resource test",
  async fn() {
    await Deno.open("hello.txt");
  },
  sanitizeResources: false,
});

Async operation sanitizer Jump to heading

The async operation sanitizer ensures that all async operations started in a test are completed before the test ends. This is important because if an async operation is not awaited, the test will end before the operation is completed, and the test will be marked as successful even if the operation may have actually failed.

You should always await all async operations in your tests. For example:

Deno.test({
  name: "async operation test",
  async fn() {
    await new Promise((resolve) => setTimeout(resolve, 1000));
  },
});

This sanitizer is enabled by default, but can be disabled with sanitizeOps: false:

Deno.test({
  name: "leaky operation test",
  fn() {
    crypto.subtle.digest(
      "SHA-256",
      new TextEncoder().encode("a".repeat(100000000)),
    );
  },
  sanitizeOps: false,
});

Exit sanitizer Jump to heading

The exit sanitizer ensures that tested code doesn’t call Deno.exit(), which could signal a false test success.

This sanitizer is enabled by default, but can be disabled with sanitizeExit: false.

Deno.test({
  name: "false success",
  fn() {
    Deno.exit(0);
  },
  sanitizeExit: false,
});

// This test never runs, because the process exits during "false success" test
Deno.test({
  name: "failing test",
  fn() {
    throw new Error("this test fails");
  },
});

Snapshot testing Jump to heading

The Deno Standard Library includes a snapshot module that allows developers to write tests by comparing values against reference snapshots. These snapshots are serialized representations of the original values and are stored alongside the test files.

Snapshot testing enables catching a wide array of bugs with very little code. It is particularly helpful in situations where it is difficult to precisely express what should be asserted, without requiring a prohibitive amount of code, or where the assertions a test makes are expected to change often.